import traceback
from collections import Counter
from datetime import datetime

from mg_app_framework import get_logger, get_store

from common_bind_service.constant.container_const import ContainerOperate
from common_bind_service.dao.code_generation_dao import RuleCodeGenerationDao
from common_bind_service.dao.container_status_dao import ContainerStatusDao
from common_bind_service.models.container import ContainerStatus, ProductInfo
from common_bind_service.models.serializer.response import Response
from common_bind_service.models.param.container_param import CheckContainerUsefulParam, ContainerRemoveProdParam, \
    ContainerBindParam, ContainerUnBindParam, ContainerListParam
from common_bind_service.service.container_mark_config_service import ContainerMarkConfigService
from common_bind_service.service.fms_data_service import FMSDataService


class UpperPcService(object):

    def __init__(self):
        self.container_status_dao = ContainerStatusDao()
        self.container_mark_config_service = ContainerMarkConfigService()
        self.rule_code_dao = RuleCodeGenerationDao()
        self.fms_service = FMSDataService()

    async def vehicle_check(self, param: CheckContainerUsefulParam) -> Response:
        get_logger().info(f'vehicle_check param: {param}')
        config, err = await self.container_mark_config_service.check_container_mark(param.vehicle_no)
        if err:
            return Response.fail(err)

        container = await self.container_status_dao.get_one_container_mark(param.vehicle_no)
        if container and container.count > 0:
            return Response.fail(f'容器{param.vehicle_no}非空, 正在使用中')
        if param.process_code == 'C130':
            return Response.success()
        fms_url = get_store().get_fms_url()
        if not fms_url:
            return Response.success()
        fms_post_data = {'data': {'trayNo': param.vehicle_no}, 'resourceNo': 'GET_TRAY_CHECK_STATE'}
        try:
            fetch_data, err = await self.fms_service.fetch_fms_data(fms_url, fms_post_data)
            if err:
                return Response.fail(err)
            state = fetch_data['data']['state']
            if state != 2:
                return Response.fail('FMS校验托盘正在使用中')
        except Exception as e:
            get_logger().error(f'调用fms获取数据失败,:{traceback.format_exc()}')

        return Response.success()

    async def vehicle_bind(self, param: ContainerBindParam) -> Response:

        # todo: 校验容器内的码值情况, 码值是否合格, 码值是否正常
        get_logger().info(f'vehicle_bind param: {param}')

        config, err = await self.container_mark_config_service.check_container_mark(param.vehicle_no)
        if err:
            return Response.fail(err)

        if not param.cell_list:
            return Response.fail('绑定码值不能为空')

        now = datetime.now()
        # 校验码值是否存在于其他容器
        errors = []
        prod_mark_list = [x.cell_sn for x in param.cell_list]
        prod_counter = Counter(prod_mark_list)
        if len(prod_mark_list) != len(prod_counter):
            repeat_marks = [x for x in prod_counter if prod_counter[x] > 1]
            errors.append(f'存在重复单件条码, {",".join(repeat_marks)}')
        rule_code_map = {}  # rule_code_record 表中的code和bind_code的映射关系
        if param.process_code == 'C220':
            rule_codes = await self.rule_code_dao.get_by_query({'wip_type': 2, 'bind_code': {'$in': prod_mark_list}})
            rule_code_map = {i.bind_code: i.code for i in rule_codes}
            # 找不到对应的bind_code,暂时用原码cell_sn组盘
            prod_mark_list = [rule_code_map[i] if rule_code_map.get(i) else i for i in prod_mark_list]

        r = await self.container_status_dao.get_container_dict_by_mark_list(product_mark_list=prod_mark_list)
        for c_mark, exit_marks in r.items():
            errors.append(f'{",".join(exit_marks)}已经存在于容器{c_mark}中')

        container = await self.container_status_dao.get_one_container_mark(param.vehicle_no)

        # 校验容器数量限制问题
        current_num = 0 if not container else container.count
        if current_num + len(prod_mark_list) > config.max_count:
            errors.append(f'超出容器最大承载数量{config.max_count}限制, 当前已承载: {current_num}')

        if errors:
            return Response.fail(' || '.join(errors))
        if param.process_code != 'C130':
            fms_url = get_store().get_fms_url()  # 在组盘之前,调用fms接口,校验成功继续组盘
            if fms_url:
                fms_post_data, err = await self.fms_service.handle_process_post_data(param)
                if err:
                    return Response.fail(err)
                _, fms_err = await self.fms_service.fetch_fms_data(fms_url, fms_post_data)
                if fms_err:
                    return Response.fail(fms_err)
        if not container:
            product_list = []
            for cell in param.cell_list:
                mark = rule_code_map.get(cell.cell_sn, cell.cell_sn) if rule_code_map else cell.cell_sn
                product_list.append(ProductInfo(mark=mark, slot_no=cell.slot_no))
            container = ContainerStatus(
                container_mark=param.vehicle_no, count=len(param.cell_list), product_list=product_list,
                last_update_time=now, last_operate=ContainerOperate.bind, last_upperpc_code=param.upperpc_code,
                last_equip_code=param.equip_code
            )
            await self.container_status_dao.insert_one(container)
        else:
            current_list = [x.dict() for x in container.product_list]
            for cell in param.cell_list:
                mark = rule_code_map.get(cell.cell_sn, cell.cell_sn) if rule_code_map else cell.cell_sn
                current_list.append({
                    'mark': mark,
                    'slot_no': cell.slot_no
                })
            update_data = {
                'last_update_time': now,
                'last_operate': ContainerOperate.bind,
                'last_upperpc_code': param.upperpc_code,
                'last_equip_code': param.equip_code,
                'product_list': current_list,
                'count': len(param.cell_list) + container.count
            }

            await self.container_status_dao.update_by_id(container.id, update_data)

        return Response.success()

    # async def check_marks(self, product_marks: List[str]):
    #     """校验产品码各种清理"""
    async def vehicle_unbind(self, param: ContainerUnBindParam) -> Response:
        get_logger().info(f'vehicle_unbind param: {param}')
        config, err = await self.container_mark_config_service.check_container_mark(param.vehicle_no)
        if err:
            return Response.fail(err)

        container = await self.container_status_dao.get_one_container_mark(param.vehicle_no)
        if not container or container.count == 0:
            return Response.fail(f'当前容器{param.vehicle_no}为空')
        if param.process_code != 'C130':
            fms_url = get_store().get_fms_url()  # 在组盘之前,调用fms接口,校验成功继续组盘
            if fms_url:
                fms_post_data = {
                    'data': {'trayNo': param.vehicle_no},
                    'resourceNo': 'FMS_CYC_UNBIND_TRAY_ALL'
                }
                _, err = await self.fms_service.fetch_fms_data(fms_url, fms_post_data)
                if err:
                    return Response.fail(err)
        if not param.cell_list:
            # todo: 需要校验容器是否出站
            update_data = {
                'last_update_time': datetime.now(),
                'last_operate': ContainerOperate.unbind,
                'last_upperpc_code': param.upperpc_code,
                'last_equip_code': param.equip_code,
                'product_list': [],
                'count': 0
            }
        else:
            # 校验被剔除的码是否存在与当前箱子
            product_mark_list_in = [x.mark for x in container.product_list]
            to_remove_marks = []
            not_exist_codes = []
            for cell in param.cell_list:
                if cell.cell_sn not in product_mark_list_in:
                    not_exist_codes.append(cell.cell_sn)

                to_remove_marks.append(cell.cell_sn)

            if not_exist_codes:
                return Response.fail(f"码{','.join(not_exist_codes)}在当前容器{param.vehicle_no}不存在")

            new_product_list = []
            for origin_prod in container.product_list:
                if origin_prod.mark in to_remove_marks:
                    continue
                new_product_list.append({
                    'mark': origin_prod.mark,
                    'slot_no': origin_prod.slot_no
                })
            update_data = {
                'last_update_time': datetime.now(),
                'last_operate': ContainerOperate.unbind,
                'last_upperpc_code': param.upperpc_code,
                'last_equip_code': param.equip_code,
                'product_list': new_product_list,
                'count': len(new_product_list)
            }

        await self.container_status_dao.update_by_id(container.id, update_data)

        get_logger().info(f'解绑容器{param.vehicle_no}成功, 容器码列表: {container.product_list}')

        return Response.success()

    async def vehicle_ng_out(self, param: ContainerRemoveProdParam) -> Response:
        get_logger().info(f'vehicle_ng_out param: {param}')
        config, err = await self.container_mark_config_service.check_container_mark(param.vehicle_no)
        if err:
            return Response.fail(err)

        # todo: 是否需要校验被剔除的ng码?? 还有其他情况不允许剔除吗? ng后是否需要在不合格品管理里面添加一条记录, 后续工序进行拦截console.logconsole.log

        container = await self.container_status_dao.get_one_container_mark(param.vehicle_no)
        if not container:
            return Response.fail(f'容器{param.vehicle_no}为空')
        # 校验被剔除的码是否存在与当前箱子
        product_mark_list_in = [x.mark for x in container.product_list]
        to_remove_marks = []
        not_exist_codes = []
        for cell in param.cell_list:
            if cell.cell_sn not in product_mark_list_in:
                not_exist_codes.append(cell.cell_sn)

            to_remove_marks.append(cell.cell_sn)

        if not_exist_codes:
            return Response.fail(f"码{','.join(not_exist_codes)}在当前容器{param.vehicle_no}不存在")

        new_product_list = []
        for origin_prod in container.product_list:
            if origin_prod.mark in to_remove_marks:
                continue
            new_product_list.append({
                'mark': origin_prod.mark,
                'slot_no': origin_prod.slot_no
            })

        # 删除容器里的码
        update_data = {
            'last_update_time': datetime.now(),
            'last_operate': ContainerOperate.remove_prod,
            'last_upperpc_code': param.upperpc_code,
            'last_equip_code': param.equip_code,
            'product_list': new_product_list,
            'count': len(new_product_list)
        }

        await self.container_status_dao.update_by_id(container.id, update_data)

        get_logger().info(f'剔除容器ng码成功: {param.vehicle_no} -> {to_remove_marks}')
        return Response.success()

    async def vehicle_cell_list(self, param: ContainerListParam) -> Response:
        container = await self.container_status_dao.get_one_container_mark(param.vehicle_no)
        res = []

        if container:
            for item in container.product_list:
                append_item = {
                    'mark': item.mark,
                    'slot_no': item.slot_no,
                    'ng_code': item.ng_code,
                    'is_ng': item.is_ng,
                }
                res.append(append_item)

        return Response.success(res)
